agora inbox for pgsql-hackers@postgresql.org  
help / color / mirror / Atom feed
[PATCH 3/3] WIP: slab performance.
361+ messages / 2 participants
[nested] [flat]

* [PATCH 3/3] WIP: slab performance.
@ 2021-07-19 20:48  Andres Freund <andres@anarazel.de>
  0 siblings, 0 replies; 361+ messages in thread

From: Andres Freund @ 2021-07-19 20:48 UTC (permalink / raw)

Discussion: https://postgr.es/m/20210717194333.mr5io3zup3kxahfm@alap3.anarazel.de
---
 src/backend/utils/mmgr/slab.c | 434 ++++++++++++++++++++++------------
 1 file changed, 285 insertions(+), 149 deletions(-)

diff --git a/src/backend/utils/mmgr/slab.c b/src/backend/utils/mmgr/slab.c
index e4b8275045..c979553dcd 100644
--- a/src/backend/utils/mmgr/slab.c
+++ b/src/backend/utils/mmgr/slab.c
@@ -23,15 +23,18 @@
  *	global (context) level. This is possible as the chunk size (and thus also
  *	the number of chunks per block) is fixed.
  *
- *	On each block, free chunks are tracked in a simple linked list. Contents
- *	of free chunks is replaced with an index of the next free chunk, forming
- *	a very simple linked list. Each block also contains a counter of free
- *	chunks. Combined with the local block-level freelist, it makes it trivial
- *	to eventually free the whole block.
+ *	On each block, never allocated chunks are tracked by a simple offset, and
+ *	free chunks are tracked in a simple linked list. The offset approach
+ *	avoids needing to iterate over all chunks when allocating a new block,
+ *	which would cause page faults and cache pollution. Contents of free chunks
+ *	is replaced with a pointer to the next free chunk, forming a very simple
+ *	linked list. Each block also contains a counter of free chunks. Combined
+ *	with the local block-level freelist, it makes it trivial to eventually
+ *	free the whole block.
  *
  *	At the context level, we use 'freelist' to track blocks ordered by number
  *	of free chunks, starting with blocks having a single allocated chunk, and
- *	with completely full blocks on the tail.
+ *	with completely full blocks on the tail. XXX
  *
  *	This also allows various optimizations - for example when searching for
  *	free chunk, the allocator reuses space from the fullest blocks first, in
@@ -44,7 +47,7 @@
  *	case this performs as if the pointer was not maintained.
  *
  *	We cache the freelist index for the blocks with the fewest free chunks
- *	(minFreeChunks), so that we don't have to search the freelist on every
+ *	(minFreeChunkIndex), so that we don't have to search the freelist on every
  *	SlabAlloc() call, which is quite expensive.
  *
  *-------------------------------------------------------------------------
@@ -56,6 +59,17 @@
 #include "utils/memdebug.h"
 #include "utils/memutils.h"
 
+struct SlabBlock;
+struct SlabChunk;
+
+/*
+ * Number of actual freelists + 1, for full blocks. Full blocks are always at
+ * offset 0.
+ */
+#define SLAB_FREELIST_COUNT 9
+
+#define SLAB_RETAIN_EMPTY_BLOCK_COUNT 10
+
 /*
  * SlabContext is a specialized implementation of MemoryContext.
  */
@@ -68,13 +82,16 @@ typedef struct SlabContext
 	Size		blockSize;		/* block size */
 	Size		headerSize;		/* allocated size of context header */
 	int			chunksPerBlock; /* number of chunks per block */
-	int			minFreeChunks;	/* min number of free chunks in any block */
+	int			minFreeChunksIndex;	/* min number of free chunks in any block XXX */
 	int			nblocks;		/* number of blocks allocated */
 #ifdef MEMORY_CONTEXT_CHECKING
 	bool	   *freechunks;		/* bitmap of free chunks in a block */
 #endif
+	int			nfreeblocks;
+	dlist_head	freeblocks;
+	int			freelist_shift;
 	/* blocks with free space, grouped by number of free chunks: */
-	dlist_head	freelist[FLEXIBLE_ARRAY_MEMBER];
+	dlist_head	freelist[SLAB_FREELIST_COUNT];
 } SlabContext;
 
 /*
@@ -83,13 +100,15 @@ typedef struct SlabContext
  *
  * node: doubly-linked list of blocks in global freelist
  * nfree: number of free chunks in this block
- * firstFreeChunk: index of the first free chunk
+ * firstFreeChunk: first free chunk
  */
 typedef struct SlabBlock
 {
 	dlist_node	node;			/* doubly-linked list */
-	int			nfree;			/* number of free chunks */
-	int			firstFreeChunk; /* index of the first free chunk in the block */
+	int			nfree;			/* number of chunks on freelist + unused */
+	int			nunused;		/* number of unused chunks */
+	struct SlabChunk  *unused;	/* */
+	struct SlabChunk  *firstFreeChunk; /* first free chunk in the block */
 } SlabBlock;
 
 /*
@@ -123,6 +142,35 @@ typedef struct SlabChunk
 #define SlabChunkIndex(slab, block, chunk)	\
 	(((char *) chunk - SlabBlockStart(block)) / slab->fullChunkSize)
 
+static inline uint8
+SlabFreelistIndex(SlabContext *slab, int freecount)
+{
+	uint8 index;
+
+	Assert(freecount <= slab->chunksPerBlock);
+
+	/*
+	 * Ensure, without a branch, that index 0 is only used for blocks entirely
+	 * without free chunks.
+	 * XXX: There probably is a cheaper way to do this. Needing to shift twice
+	 * by slab->freelist_shift isn't great.
+	 */
+	index = (freecount + (1 << slab->freelist_shift) - 1) >> slab->freelist_shift;
+
+	if (freecount == 0)
+		Assert(index == 0);
+	else
+		Assert(index > 0 && index < (SLAB_FREELIST_COUNT));
+
+	return index;
+}
+
+static inline dlist_head*
+SlabFreelist(SlabContext *slab, int freecount)
+{
+	return &slab->freelist[SlabFreelistIndex(slab, freecount)];
+}
+
 /*
  * These functions implement the MemoryContext API for Slab contexts.
  */
@@ -179,7 +227,6 @@ SlabContextCreate(MemoryContext parent,
 {
 	int			chunksPerBlock;
 	Size		fullChunkSize;
-	Size		freelistSize;
 	Size		headerSize;
 	SlabContext *slab;
 	int			i;
@@ -192,11 +239,11 @@ SlabContextCreate(MemoryContext parent,
 					 "padding calculation in SlabChunk is wrong");
 
 	/* Make sure the linked list node fits inside a freed chunk */
-	if (chunkSize < sizeof(int))
-		chunkSize = sizeof(int);
+	if (chunkSize < MAXALIGN(sizeof(void *)))
+		chunkSize = MAXALIGN(sizeof(void *));
 
 	/* chunk, including SLAB header (both addresses nicely aligned) */
-	fullChunkSize = sizeof(SlabChunk) + MAXALIGN(chunkSize);
+	fullChunkSize = sizeof(SlabChunk) + chunkSize;
 
 	/* Make sure the block can store at least one chunk. */
 	if (blockSize < fullChunkSize + sizeof(SlabBlock))
@@ -206,16 +253,14 @@ SlabContextCreate(MemoryContext parent,
 	/* Compute maximum number of chunks per block */
 	chunksPerBlock = (blockSize - sizeof(SlabBlock)) / fullChunkSize;
 
-	/* The freelist starts with 0, ends with chunksPerBlock. */
-	freelistSize = sizeof(dlist_head) * (chunksPerBlock + 1);
-
 	/*
 	 * Allocate the context header.  Unlike aset.c, we never try to combine
 	 * this with the first regular block; not worth the extra complication.
+	 * XXX: What's the evidence for that?
 	 */
 
 	/* Size of the memory context header */
-	headerSize = offsetof(SlabContext, freelist) + freelistSize;
+	headerSize = sizeof(SlabContext);
 
 #ifdef MEMORY_CONTEXT_CHECKING
 
@@ -249,17 +294,34 @@ SlabContextCreate(MemoryContext parent,
 	slab->blockSize = blockSize;
 	slab->headerSize = headerSize;
 	slab->chunksPerBlock = chunksPerBlock;
-	slab->minFreeChunks = 0;
+	slab->minFreeChunksIndex = 0;
 	slab->nblocks = 0;
+	slab->nfreeblocks = 0;
+
+	/*
+	 * Compute a shift that guarantees that shifting chunksPerBlock with it
+	 * yields is smaller than SLAB_FREELIST_COUNT - 1 (one freelist is used for full blocks).
+	 */
+	slab->freelist_shift = 0;
+	while ((slab->chunksPerBlock >> slab->freelist_shift) >= (SLAB_FREELIST_COUNT - 1))
+		slab->freelist_shift++;
+
+#if 0
+	elog(LOG, "freelist shift for %d chunks of size %zu is %d, block size %zu",
+		 slab->chunksPerBlock, slab->fullChunkSize, slab->freelist_shift,
+		 slab->blockSize);
+#endif
+
+	dlist_init(&slab->freeblocks);
 
 	/* initialize the freelist slots */
-	for (i = 0; i < (slab->chunksPerBlock + 1); i++)
+	for (i = 0; i < SLAB_FREELIST_COUNT; i++)
 		dlist_init(&slab->freelist[i]);
 
 #ifdef MEMORY_CONTEXT_CHECKING
 	/* set the freechunks pointer right after the freelists array */
 	slab->freechunks
-		= (bool *) slab + offsetof(SlabContext, freelist) + freelistSize;
+		= (bool *) slab + sizeof(SlabContext);
 #endif
 
 	/* Finally, do the type-independent part of context creation */
@@ -292,8 +354,27 @@ SlabReset(MemoryContext context)
 	SlabCheck(context);
 #endif
 
+	/* release retained empty blocks */
+	{
+		dlist_mutable_iter miter;
+
+		dlist_foreach_modify(miter, &slab->freeblocks)
+		{
+			SlabBlock  *block = dlist_container(SlabBlock, node, miter.cur);
+
+			dlist_delete(miter.cur);
+
+#ifdef CLOBBER_FREED_MEMORY
+			wipe_mem(block, slab->blockSize);
+#endif
+			free(block);
+			slab->nblocks--;
+			context->mem_allocated -= slab->blockSize;
+		}
+	}
+
 	/* walk over freelists and free the blocks */
-	for (i = 0; i <= slab->chunksPerBlock; i++)
+	for (i = 0; i < SLAB_FREELIST_COUNT; i++)
 	{
 		dlist_mutable_iter miter;
 
@@ -312,7 +393,7 @@ SlabReset(MemoryContext context)
 		}
 	}
 
-	slab->minFreeChunks = 0;
+	slab->minFreeChunksIndex = 0;
 
 	Assert(slab->nblocks == 0);
 	Assert(context->mem_allocated == 0);
@@ -342,7 +423,6 @@ SlabAlloc(MemoryContext context, Size size, int flags)
 	SlabContext *slab = castNode(SlabContext, context);
 	SlabBlock  *block;
 	SlabChunk  *chunk;
-	int			idx;
 
 	Assert(slab);
 
@@ -352,8 +432,8 @@ SlabAlloc(MemoryContext context, Size size, int flags)
 	 * sense...
 	 */
 
-	Assert((slab->minFreeChunks >= 0) &&
-		   (slab->minFreeChunks < slab->chunksPerBlock));
+	Assert((slab->minFreeChunksIndex >= 0) &&
+		   (slab->minFreeChunksIndex < SLAB_FREELIST_COUNT));
 
 	/* make sure we only allow correct request size */
 	if (size != slab->chunkSize)
@@ -367,58 +447,88 @@ SlabAlloc(MemoryContext context, Size size, int flags)
 	 * slab->minFreeChunks == 0 means there are no blocks with free chunks,
 	 * thanks to how minFreeChunks is updated at the end of SlabAlloc().
 	 */
-	if (slab->minFreeChunks == 0)
+	if (unlikely(slab->minFreeChunksIndex == 0))
 	{
-		block = (SlabBlock *) malloc(slab->blockSize);
+		if (slab->nfreeblocks > 0)
+		{
+			dlist_node *node;
 
-		if (block == NULL)
-			return NULL;
+			node = dlist_pop_head_node(&slab->freeblocks);
+			block = dlist_container(SlabBlock, node, node);
+			slab->nfreeblocks--;
+		}
+		else
+		{
+			block = (SlabBlock *) malloc(slab->blockSize);
 
-		block->nfree = slab->chunksPerBlock;
-		block->firstFreeChunk = 0;
+			if (unlikely(block == NULL))
+				return NULL;
 
-		/*
-		 * Put all the chunks on a freelist. Walk the chunks and point each
-		 * one to the next one.
-		 */
-		for (idx = 0; idx < slab->chunksPerBlock; idx++)
-		{
-			chunk = SlabBlockGetChunk(slab, block, idx);
-			*(int32 *) SlabChunkGetPointer(chunk) = (idx + 1);
+			slab->nblocks += 1;
+			context->mem_allocated += slab->blockSize;
 		}
 
+		block->nfree = slab->chunksPerBlock;
+		block->firstFreeChunk = NULL;
+		block->nunused = slab->chunksPerBlock;
+		block->unused = (SlabChunk *) SlabBlockStart(block);
+
+		slab->minFreeChunksIndex = SlabFreelistIndex(slab, slab->chunksPerBlock);
+
 		/*
 		 * And add it to the last freelist with all chunks empty.
 		 *
 		 * We know there are no blocks in the freelist, otherwise we wouldn't
 		 * need a new block.
 		 */
-		Assert(dlist_is_empty(&slab->freelist[slab->chunksPerBlock]));
+		Assert(dlist_is_empty(SlabFreelist(slab, slab->chunksPerBlock)));
 
-		dlist_push_head(&slab->freelist[slab->chunksPerBlock], &block->node);
+		dlist_push_head(SlabFreelist(slab, slab->chunksPerBlock),
+						&block->node);
 
-		slab->minFreeChunks = slab->chunksPerBlock;
-		slab->nblocks += 1;
-		context->mem_allocated += slab->blockSize;
+		chunk = block->unused;
+		block->unused = (SlabChunk *)(((char *) block->unused) + slab->fullChunkSize);
+		block->nunused--;
 	}
+	else
+	{
+		/* grab the block from the freelist */
+		block = dlist_head_element(SlabBlock, node,
+								   &slab->freelist[slab->minFreeChunksIndex]);
 
-	/* grab the block from the freelist (even the new block is there) */
-	block = dlist_head_element(SlabBlock, node,
-							   &slab->freelist[slab->minFreeChunks]);
+		/* make sure we actually got a valid block, with matching nfree */
+		Assert(block != NULL);
+		Assert(slab->minFreeChunksIndex == SlabFreelistIndex(slab, block->nfree));
+		Assert(block->nfree > 0);
 
-	/* make sure we actually got a valid block, with matching nfree */
-	Assert(block != NULL);
-	Assert(slab->minFreeChunks == block->nfree);
-	Assert(block->nfree > 0);
+		/* we know index of the first free chunk in the block */
+		if (block->nunused > 0)
+		{
+			chunk = block->unused;
+			block->unused = (SlabChunk *)(((char *) block->unused) + slab->fullChunkSize);
+			block->nunused--;
+		}
+		else
+		{
+			chunk = block->firstFreeChunk;
 
-	/* we know index of the first free chunk in the block */
-	idx = block->firstFreeChunk;
+			/*
+			 * Remove the chunk from the freelist head. The index of the next free
+			 * chunk is stored in the chunk itself.
+			 */
+			VALGRIND_MAKE_MEM_DEFINED(SlabChunkGetPointer(chunk), sizeof(void*));
+			block->firstFreeChunk = *(SlabChunk **) SlabChunkGetPointer(chunk);
+		}
+	}
 
-	/* make sure the chunk index is valid, and that it's marked as empty */
-	Assert((idx >= 0) && (idx < slab->chunksPerBlock));
+	/* make sure the chunk is in the block and that it's marked as empty (XXX?) */
+	Assert((char *) chunk >= SlabBlockStart(block));
+	Assert((char *) chunk < (((char *)block) + slab->blockSize));
 
-	/* compute the chunk location block start (after the block header) */
-	chunk = SlabBlockGetChunk(slab, block, idx);
+	Assert(block->firstFreeChunk == NULL || (
+			   (block->firstFreeChunk >= (SlabChunk *) SlabBlockStart(block)) &&
+			   block->firstFreeChunk <= (SlabChunk *) (((char *)block) + slab->blockSize))
+		);
 
 	/*
 	 * Update the block nfree count, and also the minFreeChunks as we've
@@ -426,54 +536,51 @@ SlabAlloc(MemoryContext context, Size size, int flags)
 	 * (because that's how we chose the block).
 	 */
 	block->nfree--;
-	slab->minFreeChunks = block->nfree;
 
-	/*
-	 * Remove the chunk from the freelist head. The index of the next free
-	 * chunk is stored in the chunk itself.
-	 */
-	VALGRIND_MAKE_MEM_DEFINED(SlabChunkGetPointer(chunk), sizeof(int32));
-	block->firstFreeChunk = *(int32 *) SlabChunkGetPointer(chunk);
+	/* Prepare to initialize the chunk header. */
+	VALGRIND_MAKE_MEM_UNDEFINED(chunk, sizeof(SlabChunk));
 
-	Assert(block->firstFreeChunk >= 0);
-	Assert(block->firstFreeChunk <= slab->chunksPerBlock);
+	chunk->block = block;
+	chunk->slab = slab;
 
-	Assert((block->nfree != 0 &&
-			block->firstFreeChunk < slab->chunksPerBlock) ||
-		   (block->nfree == 0 &&
-			block->firstFreeChunk == slab->chunksPerBlock));
+	Assert(slab->minFreeChunksIndex == SlabFreelistIndex(slab, block->nfree + 1));
 
 	/* move the whole block to the right place in the freelist */
-	dlist_delete(&block->node);
-	dlist_push_head(&slab->freelist[block->nfree], &block->node);
-
-	/*
-	 * And finally update minFreeChunks, i.e. the index to the block with the
-	 * lowest number of free chunks. We only need to do that when the block
-	 * got full (otherwise we know the current block is the right one). We'll
-	 * simply walk the freelist until we find a non-empty entry.
-	 */
-	if (slab->minFreeChunks == 0)
+	if (unlikely(SlabFreelistIndex(slab, block->nfree) != slab->minFreeChunksIndex))
 	{
-		for (idx = 1; idx <= slab->chunksPerBlock; idx++)
+		slab->minFreeChunksIndex = SlabFreelistIndex(slab, block->nfree);
+		dlist_delete(&block->node);
+		dlist_push_head(SlabFreelist(slab, block->nfree), &block->node);
+
+
+		/*
+		 * And finally update minFreeChunks, i.e. the index to the block with the
+		 * lowest number of free chunks. We only need to do that when the block
+		 * got full (otherwise we know the current block is the right one). We'll
+		 * simply walk the freelist until we find a non-empty entry.
+		 */
+		if (slab->minFreeChunksIndex == 0)
 		{
-			if (dlist_is_empty(&slab->freelist[idx]))
-				continue;
+			for (int idx = 1; idx < SLAB_FREELIST_COUNT; idx++)
+			{
+				if (dlist_is_empty(&slab->freelist[idx]))
+					continue;
 
-			/* found a non-empty freelist */
-			slab->minFreeChunks = idx;
-			break;
+				/* found a non-empty freelist */
+				slab->minFreeChunksIndex = idx;
+				break;
+			}
 		}
 	}
 
+#if 0
+	/*
+	 * FIXME: I don't understand what this ever did? It should be unreachable
+	 * I think?
+	 */
 	if (slab->minFreeChunks == slab->chunksPerBlock)
 		slab->minFreeChunks = 0;
-
-	/* Prepare to initialize the chunk header. */
-	VALGRIND_MAKE_MEM_UNDEFINED(chunk, sizeof(SlabChunk));
-
-	chunk->block = block;
-	chunk->slab = slab;
+#endif
 
 #ifdef MEMORY_CONTEXT_CHECKING
 	/* slab mark to catch clobber of "unused" space */
@@ -496,6 +603,61 @@ SlabAlloc(MemoryContext context, Size size, int flags)
 	return SlabChunkGetPointer(chunk);
 }
 
+static void pg_noinline
+SlabFreeSlow(SlabContext *slab, SlabBlock  *block)
+{
+	dlist_delete(&block->node);
+
+	/*
+	 * See if we need to update the minFreeChunks field for the slab - we only
+	 * need to do that if the block had that number of free chunks
+	 * before we freed one. In that case, we check if there still are blocks
+	 * in the original freelist and we either keep the current value (if there
+	 * still are blocks) or increment it by one (the new block is still the
+	 * one with minimum free chunks).
+	 *
+	 * The one exception is when the block will get completely free - in that
+	 * case we will free it, se we can't use it for minFreeChunks. It however
+	 * means there are no more blocks with free chunks.
+	 */
+	if (slab->minFreeChunksIndex == SlabFreelistIndex(slab, block->nfree - 1))
+	{
+		/* Have we removed the last chunk from the freelist? */
+		if (dlist_is_empty(&slab->freelist[slab->minFreeChunksIndex]))
+		{
+			/* but if we made the block entirely free, we'll free it */
+			if (block->nfree == slab->chunksPerBlock)
+				slab->minFreeChunksIndex = 0;
+			else
+				slab->minFreeChunksIndex++;
+		}
+	}
+
+	/* If the block is now completely empty, free it (in a way). */
+	if (block->nfree == slab->chunksPerBlock)
+	{
+		/*
+		 * To avoid constantly freeing/allocating blocks in bursty patterns
+		 * (on most crucially cases of repeatedly allocating and freeing a
+		 * single chunk), retain a small number of blocks.
+		 */
+		if (slab->nfreeblocks < SLAB_RETAIN_EMPTY_BLOCK_COUNT)
+		{
+			dlist_push_head(&slab->freeblocks, &block->node);
+			slab->nfreeblocks++;
+		}
+		else
+		{
+			slab->nblocks--;
+			slab->header.mem_allocated -= slab->blockSize;
+			free(block);
+		}
+	}
+	else
+		dlist_push_head(SlabFreelist(slab, block->nfree), &block->node);
+
+}
+
 /*
  * SlabFree
  *		Frees allocated memory; memory is removed from the slab.
@@ -503,7 +665,6 @@ SlabAlloc(MemoryContext context, Size size, int flags)
 static void
 SlabFree(MemoryContext context, void *pointer)
 {
-	int			idx;
 	SlabContext *slab = castNode(SlabContext, context);
 	SlabChunk  *chunk = SlabPointerGetChunk(pointer);
 	SlabBlock  *block = chunk->block;
@@ -516,61 +677,26 @@ SlabFree(MemoryContext context, void *pointer)
 				 slab->header.name, chunk);
 #endif
 
-	/* compute index of the chunk with respect to block start */
-	idx = SlabChunkIndex(slab, block, chunk);
-
 	/* add chunk to freelist, and update block nfree count */
-	*(int32 *) pointer = block->firstFreeChunk;
-	block->firstFreeChunk = idx;
+	*(SlabChunk **) pointer = block->firstFreeChunk;
+	block->firstFreeChunk = chunk;
 	block->nfree++;
 
 	Assert(block->nfree > 0);
 	Assert(block->nfree <= slab->chunksPerBlock);
 
 #ifdef CLOBBER_FREED_MEMORY
-	/* XXX don't wipe the int32 index, used for block-level freelist */
-	wipe_mem((char *) pointer + sizeof(int32),
-			 slab->chunkSize - sizeof(int32));
+	/* XXX don't wipe the SlabChunk* index, used for block-level freelist */
+	wipe_mem((char *) pointer + sizeof(SlabChunk*),
+			 slab->chunkSize - sizeof(SlabChunk*));
 #endif
 
 	/* remove the block from a freelist */
-	dlist_delete(&block->node);
-
-	/*
-	 * See if we need to update the minFreeChunks field for the slab - we only
-	 * need to do that if there the block had that number of free chunks
-	 * before we freed one. In that case, we check if there still are blocks
-	 * in the original freelist and we either keep the current value (if there
-	 * still are blocks) or increment it by one (the new block is still the
-	 * one with minimum free chunks).
-	 *
-	 * The one exception is when the block will get completely free - in that
-	 * case we will free it, se we can't use it for minFreeChunks. It however
-	 * means there are no more blocks with free chunks.
-	 */
-	if (slab->minFreeChunks == (block->nfree - 1))
+	if (SlabFreelistIndex(slab, block->nfree) != SlabFreelistIndex(slab, block->nfree - 1))
 	{
-		/* Have we removed the last chunk from the freelist? */
-		if (dlist_is_empty(&slab->freelist[slab->minFreeChunks]))
-		{
-			/* but if we made the block entirely free, we'll free it */
-			if (block->nfree == slab->chunksPerBlock)
-				slab->minFreeChunks = 0;
-			else
-				slab->minFreeChunks++;
-		}
+		SlabFreeSlow(slab, block);
 	}
 
-	/* If the block is now completely empty, free it. */
-	if (block->nfree == slab->chunksPerBlock)
-	{
-		free(block);
-		slab->nblocks--;
-		context->mem_allocated -= slab->blockSize;
-	}
-	else
-		dlist_push_head(&slab->freelist[block->nfree], &block->node);
-
 	Assert(slab->nblocks >= 0);
 	Assert(slab->nblocks * slab->blockSize == context->mem_allocated);
 }
@@ -657,7 +783,7 @@ SlabStats(MemoryContext context,
 	/* Include context header in totalspace */
 	totalspace = slab->headerSize;
 
-	for (i = 0; i <= slab->chunksPerBlock; i++)
+	for (i = 0; i < SLAB_FREELIST_COUNT; i++)
 	{
 		dlist_iter	iter;
 
@@ -714,7 +840,7 @@ SlabCheck(MemoryContext context)
 	Assert(slab->chunksPerBlock > 0);
 
 	/* walk all the freelists */
-	for (i = 0; i <= slab->chunksPerBlock; i++)
+	for (i = 0; i < SLAB_FREELIST_COUNT; i++)
 	{
 		int			j,
 					nfree;
@@ -723,20 +849,19 @@ SlabCheck(MemoryContext context)
 		/* walk all blocks on this freelist */
 		dlist_foreach(iter, &slab->freelist[i])
 		{
-			int			idx;
 			SlabBlock  *block = dlist_container(SlabBlock, node, iter.cur);
+			SlabChunk  *cur_chunk;
 
 			/*
 			 * Make sure the number of free chunks (in the block header)
 			 * matches position in the freelist.
 			 */
-			if (block->nfree != i)
+			if (SlabFreelistIndex(slab, block->nfree) != i)
 				elog(WARNING, "problem in slab %s: number of free chunks %d in block %p does not match freelist %d",
 					 name, block->nfree, block, i);
 
 			/* reset the bitmap of free chunks for this block */
 			memset(slab->freechunks, 0, (slab->chunksPerBlock * sizeof(bool)));
-			idx = block->firstFreeChunk;
 
 			/*
 			 * Now walk through the chunks, count the free ones and also
@@ -744,20 +869,31 @@ SlabCheck(MemoryContext context)
 			 * freelist is stored within the chunks themselves, we have to
 			 * walk through the chunks and construct our own bitmap.
 			 */
-
+			cur_chunk = block->firstFreeChunk;
 			nfree = 0;
-			while (idx < slab->chunksPerBlock)
+			while (cur_chunk != NULL)
 			{
-				SlabChunk  *chunk;
+				int idx = SlabChunkIndex(slab, block, cur_chunk);
 
 				/* count the chunk as free, add it to the bitmap */
 				nfree++;
 				slab->freechunks[idx] = true;
 
 				/* read index of the next free chunk */
-				chunk = SlabBlockGetChunk(slab, block, idx);
-				VALGRIND_MAKE_MEM_DEFINED(SlabChunkGetPointer(chunk), sizeof(int32));
-				idx = *(int32 *) SlabChunkGetPointer(chunk);
+				VALGRIND_MAKE_MEM_DEFINED(SlabChunkGetPointer(cur_chunk), sizeof(SlabChunk **));
+				cur_chunk = *(SlabChunk **) SlabChunkGetPointer(cur_chunk);
+			}
+
+			cur_chunk = block->unused;
+			for (j = 0; j < block->nunused; j++)
+			{
+				int idx = SlabChunkIndex(slab, block, cur_chunk);
+
+				/* count the chunk as free, add it to the bitmap */
+				nfree++;
+				slab->freechunks[idx] = true;
+
+				cur_chunk = (SlabChunk *)(((char *) cur_chunk) + slab->fullChunkSize);
 			}
 
 			for (j = 0; j < slab->chunksPerBlock; j++)
-- 
2.31.1


--------------E83C81DCE7D6B58DF9D86C20
Content-Type: application/vnd.oasis.opendocument.spreadsheet;
 name="slab-results.ods"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
 filename="slab-results.ods"

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
vdODojxg8CuOz9jeiAym5V7gvbDuXIPffJVoeu5jenXTxfHfI5RgnDLuT+q7O3n/5/4uz/8Z
4q+0dkRsQM6jZ/qQ57TyH1VHr1BLBwi092jSBQEAAIMDAABQSwMEFAAACAAAkosBUwAAAAAA
AAAAAAAAABoAAABDb25maWd1cmF0aW9uczIvdG9vbHBhbmVsL1BLAwQUAAAIAACSiwFTAAAA
AAAAAAAAAAAAGgAAAENvbmZpZ3VyYXRpb25zMi9zdGF0dXNiYXIvUEsDBBQAAAgAAJKLAVMA
AAAAAAAAAAAAAAAcAAAAQ29uZmlndXJhdGlvbnMyL3Byb2dyZXNzYmFyL1BLAwQUAAAIAACS
iwFTAAAAAAAAAAAAAAAAGAAAAENvbmZpZ3VyYXRpb25zMi90b29sYmFyL1BLAwQUAAAIAACS
iwFTAAAAAAAAAAAAAAAAGgAAAENvbmZpZ3VyYXRpb25zMi9wb3B1cG1lbnUvUEsDBBQAAAgA
AJKLAVMAAAAAAAAAAAAAAAAYAAAAQ29uZmlndXJhdGlvbnMyL2Zsb2F0ZXIvUEsDBBQAAAgA
AJKLAVMAAAAAAAAAAAAAAAAYAAAAQ29uZmlndXJhdGlvbnMyL21lbnViYXIvUEsDBBQACAgI
AJKLAVMAAAAAAAAAAAAAAAAVAAAATUVUQS1JTkYvbWFuaWZlc3QueG1srZNNbsMgEIX3OYXF
tjK0WVUoThaReoL0ABQGBwkGxE+U3L7YihNXVaRYyo5hhu+9GWCzOzvbnCAm47EjH/SdNIDS
K4N9R74PX+0n2W1XGyfQaEiZT4umnsN0CztSInIvkkkchYPEs+Q+ACoviwPM/G89H5Vu0czA
mlzR1sN54saeTyDtCyqRa/VVCM4BohlSwnKvtZHAZ4RRabtq7i1oY6Gt5fFyN6CLtW0Q+dgR
9tDXfQigjGjzJUBHRAjWyNEQO6Gi4wzovHWaQgSh0hEgE7bEyuFY3A8KYxPL05IG7B9YMU70
wIb8IpUEOdfrTrRO7QE510GyIb0ILD3mof9Xc1O+WHi9XQdZvB563aNR6SdeUK16W6yx96hN
X+KISGv25EtNBYd2aTFUzgmD+Ib9+/HbX1BLBwjuTIbuMwEAACwEAABQSwMEFAAICAgAkosB
UwAAAAAAAAAAAAAAAAsAAABjb250ZW50LnhtbOy9XZMcR5amd69fQas16Q7N8HAP9whqutdk
kq1uZtbWNKM13bVVA0USOwUUrAA2u/fXK7MqIwiAIPvN48iT4T7PXvQOgVPAQUS4P+7n6/2n
//y3N/ff/PXu8f3rh7d/vAl/GG6+uXv78uHV67c//PHm//23//JivvnPf/pf/unh++9fv7z7
7tXDy5/e3L398OLlw9sPh///m8NPv33/3fPv/vHmp8e33z3cvn/9/ru3t2/u3n/34eV3D+/u
3q4/9d3H1t89/V3Pv/L+w9/v5R9/Mv74pz/c/e2D+sNH209+9vYv+t/8ZPzxT796vP1Z/eGj
7eGhfvzj3z+oP/y39/cvvn84PPU3724/vP7Mi7/dv37773+8+fHDh3ffffvtzz///Ief4x8e
Hn/4NizL8u3T724Ov9zs3v30eP9k9erlt3f3d8e/7P234Q/h29X2zd2HW9W/o+3HLr396c1f
7h7lR3P74fZXb/Xd4937g8nhn3v8MLU/6OOf+eT7+usP8tf11x9+4zG//PH2Uf7Onow//VTi
K/1Tia8+/tk3tx9+/I33O3/7L4fffPqff/nnX76rxzfq33W0/eRRvXx8/U7+Zz5bf/zzDw8P
m6vHH3he7E/ujsOQvn3+74+sf/5d858fX3+4e/zI/OXvmr+8vX+5PfGHN196aAe78O3B4sXd
X4+f/LaIjg/i/W/8wPjt829vxu9f/eYf/f/9yz//68sf797c/mL8+h8bv3j99v2H27e/PJnH
40v4zX/p9O3j3buHxw/bg/le33wPb2vcfPvxw5v73946jr+7mv7w+OrVF00P7sRvD9vIYRG/
+Ovru5//0yd76+9/D8u3T0Yf76e/+wNh+PZosy3Jw+v+ZfN//GHj0/cPP7199bwNPD+Mu7+9
u3t8ffyt2/unH/vukz/h4y/4/sHwR56Y9tGf8MlG//rufl392z/pi3/Mw8OLN+8PH8Pho394
991HP/0pNx7f/E37444f+sOr7z//Ez9b9C/fv48fvvRu/+3/+fb4ey+O2DyA4fQ3fXRcGG/+
tJ4NnveC999uv/D94Yzw4vvbl3cvXt29vH//p3963uO3X/7m+b+Pfv/x5v94fH17+NYO2+9q
8Ob1/d/XX//293/4n18faPP0br7519u377/wx/xvt+8e3v/vn9k9/+LNN5/80Uf7Fz/cvT08
xcNe8/7n1+/ff2Lx7vWHl4fd+K+3B8eOn+4/cO3/uvsft//9p9936yMbxaW/v/9w96bGp39+
/fanv33z9DA+vH57983//TsP7DPLr+bft7/1kZx+/fanDw8H7r1++eLpz9m+nqf//eRf8/Ih
bH/ZyfmnTeWA8fuf3ry9WX/y41988e6wGI7/prv333z/8N1fHu9u//3FX+4O6+LwBx7/6vVP
PJn//PrVEcHz8IeQ33148v8jd37bt8ff8u3x4efPHDv8ysdePf/W8Rd/vHv9w4+H1RmmP5Tj
X/77Dv/0/u7Fw7sPr9/c3r/4+Kc/PP50d47f4078/v72/v0Zjn+4/fIDX3/xzeGgeff44t3t
D3cvnn/ivx3+z389/t6fp+///K8/3t19CJ/9Cz/61z3fAF69fv/u/vbvp4d6+pOPJ5XD+f7F
m4dXhz/1/vHFh7+c4/aXn7fo9u39/cPL4/9xOKJcwfdY4/v3j3d313M9VT/22w93r67h+fSV
Ppg/j8Ofny48j6/fP3y+W7r8S/LX+Xyu/w8pX+Vj8vp3PN/Rvzv8cS+Pp8WDV7/+N/3XcNwM
T5bP/983p/864Pppsz787S/vDhenwwb2fO598/rtiy/85unHjr97PIz+cHgUr17/8PrD4XfD
0bnT7x9Pmn/6X//p24//c/uvz339nUPB3W8dCu7u79ffOTziYyzr6T9erMe1729/uv/w+aHh
8EO/htzRtxe3969/ePvi/cNPj8cA2F9v73+6e/Hh7++2V3G4p93dbvGylWdHFv78ePvuCXnH
k/TbhxfH/15/6tXrx7uXz79z/+Fx+7MenkMbL27f/nCMWg2//o2jN8c/7e3mwPsfH1+//fcX
Hx5efP/6l7//+ff+evz3HK5A68/95eHD4dS1vsjtd//HT+8/vP7+7ydgb4fLw+O7/eHg9I8f
P5qnq8Tzo/n0x47/5De3jz8c3v/93fcHT4bjmeBL3+txhfzytzz9UZ8e1w6HsofHP978p+Hp
/918/DoefvpwfziofvbvfPqt46+/+PDj48NPP/z4/M4/fVC/Njq+x09tnk6rn1xYDu48/eL7
1//z8IshnA46z7+2/iWPb06mT3/H+x9vXx3DHr/6yw8XzLvHJw8+cW/9434+HYnWP+8jl45/
+4vD3f/27erDx7/59H2ffvcLP/z85/6OwfEf/BQUu7/72y83tc/+9u33f+Pv337/tz343ETe
el/e/cZx9R+s9v/2+q8PH4777b8drY//x//58Hi4xvyD5X88yD48Ht7Vi+cF89GLOv368xc+
/mE4PItv3j/cv371zfa1/mL1uL7PT3/4w8O73/jZMx7Il89kDT+Qp1884wF8+WR3/gP4L8fo
y9n//uEPy/J7/9Kznsc/+sOens8Xjc54Xl8+Txo+mMMZ5oeHx79vKDvGBT/+kf86+H5NLk/v
y2fY/yhP79y1+eWD8j6elv/KPffpzTt+ev/ouVz/6S17BOPnv3rJk0EYrkHG+i022G5019xj
/9EH7Emo8NWOyE0+vnO3ifDVDtBXY9RVH9/XOn5fD1JXfXxf7TTuQCnpCVRT66udsPfyRM7+
Jr7WqfmSN9qvuWiqP5k9H5SriPdVAiThq52E9064r/K4xq91bP7vxxzBDr6X2gPl+LXO43t5
IGd/EV/rRG17ADtcIl/rzPw1v4hzrtbVS+JrnXov+UWc80DO/gK+1rnV9gC+6sH+6zwQz8Dw
t7/rSftB14t+uV/rsHjJL/eiD+BrHQcdNm+XgFnc44HP88IZr3vAq9oTLvNArnvgq9ojLvNA
dlll8BtP4B89vq9TdrHnuOfXf1xnfzB7rjKo2nAu87i+1unRAUHSA6hm8tc6xO7lgZz9RVz3
mLrDJXLdY2sVky/yQJLt2KoVPn/piyjp1/+G06b8+vaHh7e39y/+cv/iw+OnBa/b7304/Pbj
mUvr4z/oy7XPv/EQT/82SoopKf7HC+nyHQRf/Hb5TPlMz/lMbZdS637/xT386vv7FynEQmIh
nbOQbMGMfSykLx/DODixkK6wkGwxsH3eQFhYLKzdLCxb9LRvQnH0Y2FVLyxbnH0fC+s3E36/
jUUIxUK6zEKy5Wf2efRjYbGwdrOwbHm/vgnF0Y+FVb2wbPnjdWGt6+BX5Sfh8ymCZID4hC/z
CU91GX/7J0x2iEX2H2aR1VUD7HeREeBmke1mkdXVMvzHW2SQjUVXvejq6h64AfEJX/0Trqs4
2C83uAGxyHazyOqqD/a7yLgBsch2s8jqKhH+4y0yyMaiq150dVUL3ID4hK/+CdfVB+yXG9yA
WGS7WWTXqhVo9XAGyVhk5y6y3Gs1AzcgFt11F923v6mtfPqNvzy8+vsvet7vHu9uX70/KuL+
6Z+elsyTWvpP98+f5Pu7D0fNvlWH8uXt+8Nzv3v7/vWH13/95QN6/s1f/sLvX7999eL+9i93
9+8/szkKAD/e/XD44x9f3P3t8He/P2qMf8nq59f3r17ePr56fxK9XL17/WEV937+7ze3f3v9
5qc3L169/v77u8OGcRRoHP5w+OSf9Ca//e1/0voHPv3v6Q97/mpP+sCnX/t4fzkKD3+kfP74
5rjUHt98/E9/ePnT+48EII+/+e7d/d9fvLp7f9SQfBY/3PSOP3LhJFr9hb/1Sfv65OCTRObJ
9P2LZ93Ju1eHD2o4jqV9Nnr1vEs+731f2j4/+4sfH37+wt96VLX+zMHDn/cl944qnKto/CaN
+ceb9x8eD8/55pvjw787Kk3++vcOf/7xd9796d3th5c/Ht7W83+tr+2Xv9bNkeOv7sGP43ey
Bz8ef3q7Bzf+cv/w8t/34MjLH396uwtHDnvkqz34sSka78aZP795vwdfnjSrba78zi4f0kdY
27bv3e3nzzLYu3gNr79/2IMfbx9e3D88vLvYp/n9/cPth09/56i5/UXfnm0314KvT08nFcWt
g6GrZ8fJv4JfcXT2ar3CSb49G7t6OIVR93A19n2G4tPz9WrUvBpNXsEQGAJD3BmSNb+yt1dn
MGQ1dvVwnmJMw1H/T/BwNfZlyKIxZPFlyKwxZIYhMASGNMGQRdsFF9/9b8lnMGQ19n2j43yG
i5u1q49Je7fJmW3aSoimdQBFoAgUcadIEI+GwXY27HqXDmXIuQyD+ARXa9+om+bc5OvVRW9J
kASSQBJ/kmRxr87Ou/TpkiF7dwWSjEOadR83a9/ooHYnyb53kqNSg3JTipAEkkCSNkiyaCn2
g523X+eQZLlGln2czgnAbda+WRxtOcy+6yFpd5LEnQSSQJI2SDKKjo3+fp2zS4/XyLVDEkgC
SSAJJDnthuo+yC79q7xxOKNsa7P2/vLUD883f6Oth4mcOyyBJY2wZFbLMX2zsoe/7xyWzNfI
ucOSCpZMGksmWAJLYEkTLIlie1i09YdV9yOe15B4nY7E81oSnasXklhdlnzPCllzK9NRAktg
SSMsmcTW9sm5t306q7l9umZ3+75ZUkSWFFgCS2AJLLHv2bPYLDa7+3UOS+ZrZN5hCSyBJbAE
ljx7lsRof3KO86dwTu59s4YlsASWwBJYcg2WiPH05BxPP/x957AkXSP3DksqWKLVBGdqgmEJ
LGmEJbO4W8/O06Pmc3LvmzUsaSX3DktgCSzpiiWHHU7dCdmnv/BWz3DyF3NXL8dZG1VysHP1
a9EuTIvpvgRNoEm3NDnOYZIHNvlmudEoMd6cSj72tYgXp2dj329O6+EYTT0cFZNxxJE4MASG
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
9h3jK9OtqvYjrZz8RRIkCSIJmST2t3TruWyH2qz9qkaSX7s2U9rp2ogkSBJEEjJJJk4ClNJz
SuLV8at6nT1t+29U/JEyJ0ESJIkhCZkka3dxeWlOIpkkSIIka0lCJon9Uw2QSbJp/RE3U39E
+5k8bXWLTJKn3+BIgiTqm4ZMEvun2reW2yHuNN3VSPJLfGS2Z4UkSIIkD9xFJJNk5bv378xu
Z06CJEiymCRkkkzsG4+9tkNsZ3VX+0qi3dzYfG8l0kkeSZBkMUnIJJn4y/r9mSQvlUTr2tjo
2ogkSBJDEjJJJt6HY/vBHfe72ldkkWLnJ8ecBEmQZC1JyCQxjzFCJ/l39uQkcRdJkGQxScgk
WVsSbW+7+PbdQhIkQZK1JCGTxD7G0XvPexVv5NzVzEl+dRFfTgEjCZL87Z2jNiUZ3l1J3vmm
CZBIsm2jbWKjlKvYdYQvTST5ZB8wDMEQDHE3hEQS4xt6S/WcXIinp+9q3zG+MpHko7ckUQRF
UMRdERJJjG/onlrf5E78V7Xv+1rbGfFOJPnk3BdFUARF3BUhkWTmLU0iiVESsWEyPRuRBEmC
SEIiif0tTSKJ+SyeeAgPSZAESWJIQiLJxJnaPY3Uk9gX4KpGkl+7dqt951Y7kiBJDElIJJmQ
pOWaaxPP/d7Vvs9Re2dvpne2/TeqPTLbs0ISJEESf0lIJJm6F/SD+5JXNZL82rXTZLvpNBmS
IAmS+EtCIoldkr61LSfR4rsaSUgkQRIkWUwSEknWvnm/aT+Hzff3QPdfJEGSxSQhkcT+TYuQ
SCJugznfOBUbJnOfBEmQJIgkJJLY34cREkneKQlzEiRBkqUkIZFk4n04th/ccb+rWd36bG8e
JEESJPGXhEQS8xi/WjFuRxHvuN/VvpJ8sruVXRKyrZAESdaShEQSuyQBEkk28ZC374yTOQmS
IMlakpBIMvep6mO8q30lUQ95+0qi+da4mYgkSPLvY8pN3dn+Xem7t00miXlt6+WZJNrKVvZd
2fpod2IUQREUeUARUkmMn2mAVBLxopDzPaGPrrbhCI7gyAOOkEti/UzHV1NfOYbwu3qdv/zf
udaGIziCIw84QjLJzHu616M3tZfLVe07xld2AS7az7RwagtLsCSMJWSTTKzS9FxzVpeQrmrf
Mb6y51bVdm8qqbtYgiVhLCGdxP7XdYR0kldaQjoJlmDJapaQTzJhSYR8kld2S9nFnylrXFiC
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
Voud1ibjWqPVFh2+KVBpH2uv8AqtxsEuFi06/AbEg6Nrgw4PdB2ArsAT467LkXEMFljgXrKV
V6grFF0t0MHPkqb6Z0TXscurA13HoGsxu0oUE6+otMvG22jnLx5cvYMault7KXGdaHF9N+Kt
oWtxwdpkXGvo2qJDF2OYV8ROmZw8pu+HmzFqMLo26FA0ujbo8EDX/i85r2FUMN/HjAW1F8Pz
e87JyxFKRVcgOVh5pbpC0bX2Mvz0RLltIQ50raNrLn5VmWSP5pV171dcq7DBpbq5GFeR4vpu
xFtB11yMqzYZ1wq6NunQOeX9+xWFrm5w57AWHUpG1xYd7gdd39/m5b8/3/fxP+6SSZ7At1oZ
1zKsnv8/vs3OX3/759/+/ufff/vnL//4y10Pv/z5x9q/fJ/VP3/5/T9+/fMvv//yz//49S+/
/O1vv//6xx//+i//2y//9dsf/+cv//jHb3/9f/7zj//lr7/953/98vvf//jtn8u/mP4v+3/8
/93yf8/q+5fHIfzjt9//8sf8P/765f/yL7Ocfv9/P4rtX//lXx9SmOUFvGj9x//69b/xr//y
v/r4b/+Wfrk/JPh/XYP/6//++oP7rxvwf/3XEP8a7esEf/m8/vf6RK7+yz8+KuaD7t4k96rK
f//9118/zuJXJPznL+FZu69K/err6Tf9LyBRl5v+9uu///I///Hn64fhL4//of+2/IvnFWXP
5aqFL1e/Ju4//vz97//8j6+/Kcu/e/+W/M9f/vjz1987r1oal9uY8f/XL3/+9b//pfeZU8YJ
WALALb/AwwvHVAyeCgAypCHZrqfoVfs3wW9/+7UDBbYP5N/mV/j/EDGSv/73//nPHiNxzSP5
97//+28iHsk/pAxkJsS//fafx+Qck/NzTU6UMzntz2Rvk3N8co7JQfPhmN3Q7nj/t1//+vuv
v/zRgxEx+zEKeIb6fiPv0Cywxyh7i1Hg8+J9Wg5Y6MltDVTzaXt+XJG5pwHsVArzmRRgZQGt
sGCHkwh00uV9WnyDwmxwe2Atq+8wMG9rW9yLJRHFkAixkdir01EtistiQIT3vZoU5qSxVrHP
znw1kOviOLQJAy/va9vVZE1hWmqO1tQLUVNq0ihNjZbUjSgpNaVslPNvv4iYUp7M4e765bd+
gLF94G52Ciyg5MYcYM0cd8lcrSrjE31xTyNsgdalPhdTkglbCXVxbUNNI1D1kb0tGPCAPvPz
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
Ynin0dkJqngwWq5brveaFZIFuUW8pgoyJF69bZPN1CSILl69bVPhQEYPZCgFSEC81kGtqRZq
zdlTq37xqoi+XCry8aowXBilqOGdRmcnqOLBaLluud5rVkgW5BbxmirIkHj1Xkq2DFOogHj1
7kmFAxk9kKEUIAHxWge1plqoNWdPrfrFq6Xdd3Dj4i7yspNKCeqh4Do6N0EVEUbLdcv1TrNC
uiC3OMymCjIkXr1jks3Un4cuXr1jUuFARg9kKAVIQLzWQa2pFmrN2VOrevEqGdFHnsW2kBe6
5+TrGOvo7ARVPBgt1y3Xe80KyYLcIl5TBRkSr94zyWbq0EMXr94zqXAgowcylAIkIF7roNZU
C7Xm7KlVv3iVxI0LGXuHSlvGhaaGdxqdnaCKB6PluuV6r1khWZBbxGuqIEPi1Xsm2Uwdeuji
1XsmFQ5k9ECGUoAExGsd1JpqodacPbXqF6/E+w4y9sUW2WvVdYborbSOzk5QxYPRcl1CrlUn
mVCa+LVbR8cVr+mC3CBekwUZEq/eM8mWYf4UEK/eJKlwIKMHMpQCJCBeL6fW544pYYzqTM96
o7bgYnA7rg8wLWNc80eIlwJX9UqXekQX/SxWcauksZK4jp1G56a+IsJouS4i18Kw3iqiC946
OrLSTRbkFqWbKsiA0u29Z1KfqUMPWen23jOpcCCjBzKUAuS80v0AtZxy0kKbjpne2HPfl3PK
SYOV7geYljGu+SPES4ELpHTXZd0/3437/cOldevwBXKYdVx+JKw9/8bTx3Pz+HB793L3+HB9
f7Xw4frlOfTL9WN9uX767fBy9XT98Nvh6vr29unw/PzLp//7+vvj8/9zfX//eLME8Kebx2/f
r5/unh8fjr/5/H/En//HMf+f49+nn4O4f3y6enb/eXj3h1eOUU9/+z3ffvn0y0+Kyi+uR7b/
+PHbM3759H8p8+uv9npJE/3pjPz0L2//bHs6Jz/9oM2NEW8f8bv5+qfzH2Xwl8+/58yJeQsX
b68O//19+bjd+Of1/e72+uX66vvd/ePL1dsPns/94k8/nvTLpzcW/Me359M37vr79/s7Rwv3
6KvlD/7y6fSb9/l2+vOf/5n9ef13N4+x0x/79fXlxYX580jxp/Xf/+Wnf//Xn/7939iPf//L
+qzf3F9+e/Xy+HJ9/8unh8eHwyr/vz7+19WXu/sX9x08/oXuE7u+fz6s39jnx1c38719ZY8Q
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
bWxQSwUGAAAAAA8ADwDOAwAAMsYHAAAA
--------------E83C81DCE7D6B58DF9D86C20--





^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v7 1/3] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 +++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 208 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 ++--
 src/include/utils/guc.h                       |   6 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 +++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..62d6c0d36a2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7069,7 +7069,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7078,14 +7078,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ebc204c4462..d123b7ebb77 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1143,7 +1143,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 608f10d9412..2b309791ee7 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
 #endif
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	ListCell   *l;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach(l, elemlist)
+	{
+		char	   *tok = (char *) lfirst(l);
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	guc_free(rawstring);
+	list_free(elemlist);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 976638a58ac..c2a044321d1 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 29643c51439..15d771a16c9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fec79992c8d..2c3926cbadf 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..450180bee31 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1683,12 +1683,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index f87b558c2c6..a9ef447d1d4 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -646,6 +646,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 242862451d8..5fbb8e7ab7c 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..98b0ba08c0c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 82ac8646a8d..e6ffc96468f 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -174,5 +174,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--grg2fqqi6tkdt5me
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v7-0002-Assign-backend-type-earlier.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread

* [PATCH v10 1/2] log_min_messages per process type
@ 2025-11-04 20:18  Euler Taveira <euler@eulerto.com>
  0 siblings, 0 replies; 361+ messages in thread

From: Euler Taveira @ 2025-11-04 20:18 UTC (permalink / raw)

Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.

The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
 doc/src/sgml/config.sgml                      |  42 ++-
 src/backend/commands/extension.c              |   2 +-
 src/backend/commands/variable.c               | 256 ++++++++++++++++++
 src/backend/postmaster/launch_backend.c       |   2 +-
 src/backend/utils/error/elog.c                |   2 +-
 src/backend/utils/init/miscinit.c             |   2 +-
 src/backend/utils/misc/guc_parameters.dat     |  10 +-
 src/backend/utils/misc/guc_tables.c           |  21 +-
 src/backend/utils/misc/postgresql.conf.sample |   2 +
 src/include/postmaster/proctypelist.h         |  42 +--
 src/include/utils/guc.h                       |   5 +-
 src/include/utils/guc_hooks.h                 |   2 +
 src/test/regress/expected/guc.out             |  54 ++++
 src/test/regress/sql/guc.sql                  |  18 ++
 14 files changed, 419 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5560b95ee60..cbc748a3636 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.*    /var/log/postgresql
      <variablelist>
 
      <varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
-      <term><varname>log_min_messages</varname> (<type>enum</type>)
+      <term><varname>log_min_messages</varname> (<type>string</type>)
       <indexterm>
        <primary><varname>log_min_messages</varname> configuration parameter</primary>
       </indexterm>
@@ -7092,14 +7092,38 @@ local0.*    /var/log/postgresql
        <para>
         Controls which <link linkend="runtime-config-severity-levels">message
         levels</link> are written to the server log.
-        Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
-        <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
-        <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
-        <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
-        <literal>PANIC</literal>.  Each level includes all the levels that
-        follow it.  The later the level, the fewer messages are sent
-        to the log.  The default is <literal>WARNING</literal>.  Note that
-        <literal>LOG</literal> has a different rank here than in
+        Valid values are a comma-separated list of <literal>type:level</literal>
+        and a single <literal>level</literal>. The list allows it to use
+        different levels per process type. Only the single <literal>level</literal>
+        is mandatory (order does not matter) and it is assigned to the process
+        types that are not specified in the list.
+        Valid process types are listed in the table below, each corresponding to
+        either postmaster, an auxiliary process type or a backend.
+        <simplelist type="vert" columns="4">
+         <member><literal>archiver</literal></member>
+         <member><literal>autovacuum</literal></member>
+         <member><literal>backend</literal></member>
+         <member><literal>bgworker</literal></member>
+         <member><literal>bgwriter</literal></member>
+         <member><literal>checkpointer</literal></member>
+         <member><literal>ioworker</literal></member>
+         <member><literal>postmaster</literal></member>
+         <member><literal>syslogger</literal></member>
+         <member><literal>slotsyncworker</literal></member>
+         <member><literal>startup</literal></member>
+         <member><literal>walreceiver</literal></member>
+         <member><literal>walsender</literal></member>
+         <member><literal>walsummarizer</literal></member>
+         <member><literal>walwriter</literal></member>
+        </simplelist>
+        Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+        <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+        <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+        <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+        <literal>FATAL</literal>, and <literal>PANIC</literal>.  Each level includes
+        all the levels that follow it.  The later the level, the fewer messages are sent
+        to the log.  The default is <literal>WARNING</literal> for all process types.
+        Note that <literal>LOG</literal> has a different rank here than in
         <xref linkend="guc-client-min-messages"/>.
         Only superusers and users with the appropriate <literal>SET</literal>
         privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
 								 GUC_ACTION_SAVE, true, 0, false);
-	if (log_min_messages < WARNING)
+	if (log_min_messages[MyBackendType] < WARNING)
 		(void) set_config_option_ext("log_min_messages", "warning",
 									 PGC_SUSET, PGC_S_SESSION,
 									 BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 4440aff4925..a450254d1fa 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
 #include "utils/backend_status.h"
 #include "utils/datetime.h"
 #include "utils/fmgrprotos.h"
+#include "utils/guc.h"
 #include "utils/guc_hooks.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
@@ -41,6 +42,8 @@
 #include "utils/tzparser.h"
 #include "utils/varlena.h"
 
+static int	log_min_messages_cmp(const ListCell *a, const ListCell *b);
+
 /*
  * DATESTYLE
  */
@@ -1272,3 +1275,256 @@ check_standard_conforming_strings(bool *newval, void **extra, GucSource source)
 
 	return true;
 }
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types.  LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+	char	   *rawstring;
+	List	   *elemlist;
+	StringInfoData buf;
+	char	   *result;
+	bool		first = true;
+	int			newlevel[BACKEND_NUM_TYPES];
+	bool		assigned[BACKEND_NUM_TYPES];
+	int			genericlevel = -1;	/* -1 means not assigned */
+
+	/* Initialize the array. */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		newlevel[i] = WARNING;
+		assigned[i] = false;
+	}
+
+	/* Need a modifiable copy of string. */
+	rawstring = guc_strdup(LOG, *newval);
+
+	/* Parse string into list of identifiers. */
+	if (!SplitGUCList(rawstring, ',', &elemlist))
+	{
+		/* syntax error in list */
+		GUC_check_errdetail("List syntax is invalid.");
+		list_free(elemlist);
+		guc_free(rawstring);
+		return false;
+	}
+
+	/* Validate and assign log level and process type. */
+	foreach_ptr(char, tok, elemlist)
+	{
+		char	   *sep;
+		const struct config_enum_entry *entry;
+
+		/*
+		 * Check whether there is a process type following the log level. If
+		 * there is no separator, it means this is the generic log level. The
+		 * generic log level will be assigned to the process types that were
+		 * not informed.
+		 */
+		sep = strchr(tok, ':');
+		if (sep == NULL)
+		{
+			bool		found = false;
+
+			/* Reject duplicates for generic log level. */
+			if (genericlevel != -1)
+			{
+				GUC_check_errdetail("Generic log level was already assigned.");
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, tok) == 0)
+				{
+					genericlevel = entry->val;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+		}
+		else
+		{
+			char	   *loglevel;
+			char	   *ptype;
+			bool		found = false;
+
+			ptype = guc_malloc(LOG, (sep - tok) + 1);
+			if (!ptype)
+			{
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+			memcpy(ptype, tok, sep - tok);
+			ptype[sep - tok] = '\0';
+			loglevel = sep + 1;
+
+			/* Is the log level valid? */
+			for (entry = server_message_level_options; entry && entry->name; entry++)
+			{
+				if (pg_strcasecmp(entry->name, loglevel) == 0)
+				{
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			/*
+			 * Is the process type name valid? There might be multiple entries
+			 * per process type, don't bail out because it can assign the
+			 * value for multiple entries.
+			 */
+			found = false;
+			for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+			{
+				if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+				{
+					/* Reject duplicates for a process type. */
+					if (assigned[i])
+					{
+						GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+						guc_free(ptype);
+						guc_free(rawstring);
+						list_free(elemlist);
+						return false;
+					}
+
+					newlevel[i] = entry->val;
+					assigned[i] = true;
+					found = true;
+				}
+			}
+
+			if (!found)
+			{
+				GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+				guc_free(ptype);
+				guc_free(rawstring);
+				list_free(elemlist);
+				return false;
+			}
+
+			guc_free(ptype);
+		}
+	}
+
+	/*
+	 * The generic log level must be specified. It is the fallback value.
+	 */
+	if (genericlevel == -1)
+	{
+		GUC_check_errdetail("Generic log level was not defined.");
+		guc_free(rawstring);
+		list_free(elemlist);
+		return false;
+	}
+
+	/*
+	 * Apply the generic log level after all of the specific process types
+	 * have been assigned. Hence, it doesn't matter the order you specify the
+	 * generic log level, the final result will be the same.
+	 */
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+	{
+		if (!assigned[i])
+			newlevel[i] = genericlevel;
+	}
+
+	/*
+	 * Use a stable representation of log_min_messages. The generic level is
+	 * always the first element and the other elements (type:level) are sorted
+	 * by process type. See log_min_messages_cmp for details.
+	 */
+	list_sort(elemlist, log_min_messages_cmp);
+
+	initStringInfo(&buf);
+	foreach_ptr(char, tok, elemlist)
+	{
+		if (first)
+		{
+			appendStringInfoString(&buf, tok);
+			first = false;
+		}
+		else
+		{
+			appendStringInfo(&buf, ", %s", tok);
+		}
+	}
+
+	result = (char *) guc_malloc(LOG, buf.len + 1);
+	if (!result)
+		return false;
+	memcpy(result, buf.data, buf.len);
+	result[buf.len] = '\0';
+
+	guc_free(*newval);
+	*newval = result;
+
+	guc_free(rawstring);
+	list_free(elemlist);
+	pfree(buf.data);
+
+	/*
+	 * Pass back data for assign_log_min_messages to use.
+	 */
+	*extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+	if (!*extra)
+		return false;
+	memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+	return true;
+}
+
+static int
+log_min_messages_cmp(const ListCell *a, const ListCell *b)
+{
+	const char *s = lfirst(a);
+	const char *t = lfirst(b);
+
+	if (strchr(s, ':') == NULL)
+		return -1;
+	else if (strchr(t, ':') == NULL)
+		return 1;
+	else
+		return strcmp(s, t);
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+	for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+		log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 926fd6f2700..08553c0f024 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -178,7 +178,7 @@ typedef struct
 } child_process_kind;
 
 static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
 	[bktype] = {description, main_func, shmem_attach},
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index e6a4ef99059..4e9143b6e6f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
 static inline bool
 should_output_to_server(int elevel)
 {
-	return is_log_level_output(elevel, log_min_messages);
+	return is_log_level_output(elevel, log_min_messages[MyBackendType]);
 }
 
 /*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
 
 	switch (backendType)
 	{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach)	\
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages)	\
 		case bktype: backendDesc = description; break;
 #include "postmaster/proctypelist.h"
 #undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index f0260e6e412..3297c4bc30c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1686,12 +1686,14 @@
   options => 'server_message_level_options',
 },
 
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
   short_desc => 'Sets the message levels that are logged.',
   long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
-  variable => 'log_min_messages',
-  boot_val => 'WARNING',
-  options => 'server_message_level_options',
+  flags => 'GUC_LIST_INPUT',
+  variable => 'log_min_messages_string',
+  boot_val => '"WARNING"',
+  check_hook => 'check_log_min_messages',
+  assign_hook => 'assign_log_min_messages',
 },
 
 { name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 13c569d8790..0aebb9c8e24 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
 	{NULL, 0, false}
 };
 
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
 	{"debug5", DEBUG5, false},
 	{"debug4", DEBUG4, false},
 	{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool standard_conforming_strings = true;
 bool		current_role_is_superuser;
 
 int			log_min_error_statement = ERROR;
-int			log_min_messages = WARNING;
 int			client_min_messages = NOTICE;
 int			log_min_duration_sample = -1;
 int			log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
 static int	server_version_num;
 static char *debug_io_direct_string;
 static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
 
 #ifdef HAVE_SYSLOG
 #define	DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char	   *role_string;
 /* should be static, but guc.c needs to get at this */
 bool		in_hot_standby_guc;
 
+/*
+ * log_min_messages
+ */
+int			log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+	[bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
 
 /*
  * Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c4f92fcdac8..ad0804ccb9f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
                                         #   log
                                         #   fatal
                                         #   panic
+                                        #   and an optional comma-separated list
+                                        #   of type:level
 
 #log_min_error_statement = error        # values in order of decreasing detail:
                                         #   debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
  */
 
 /*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
  */
 
 
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..8acbdba7ff5 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
 extern PGDLLIMPORT int log_parameter_max_length;
 extern PGDLLIMPORT int log_parameter_max_length_on_error;
 extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
 extern PGDLLIMPORT int client_min_messages;
 extern PGDLLIMPORT int log_min_duration_sample;
 extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,8 @@ extern PGDLLIMPORT bool trace_sort;
 extern PGDLLIMPORT bool optimize_bounded_sort;
 #endif
 
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
 /*
  * Declarations for options for enum values
  *
@@ -344,6 +346,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
 extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
 extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
 extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
 extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
 
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index b6ecb0e769f..9c90670d9b8 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -177,5 +177,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
 extern bool check_synchronized_standby_slots(char **newval, void **extra,
 											 GucSource source);
 extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
 
 #endif							/* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
 (0 rows)
 
 DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "foo"
+DETAIL:  Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages 
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+ERROR:  invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL:  Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL:  Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL:  Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL:  Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+ERROR:  invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL:  Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+                                        log_min_messages                                         
+-------------------------------------------------------------------------------------------------
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+      log_min_messages      
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
   WHERE no_reset AND NOT no_reset_all
   ORDER BY 1;
 DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo;  -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1';  --fail
+SET log_min_messages TO 'debug1, backend:error, fatal';  -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning';  -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1';  -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
-- 
2.47.3


--r3qaph7b72ke2fw4
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="v10-0002-review.patch"



^ permalink  raw  reply  [nested|flat] 361+ messages in thread


end of thread, other threads:[~2025-11-04 20:18 UTC | newest]

Thread overview: 361+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2021-07-19 20:48 [PATCH 3/3] WIP: slab performance. Andres Freund <andres@anarazel.de>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v10 1/2] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>
2025-11-04 20:18 [PATCH v7 1/3] log_min_messages per process type Euler Taveira <euler@eulerto.com>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox